メディア展開データの量を見る#
メディア展開データを対象に、2章で取り上げた量を見るための可視化手法を再度適用してみます。
本章を通して、これまでの学びを振り返り、知識の定着を図りましょう。
2章の繰り返しになりますが、有意義なデータ可視化を行うために重要なポイントの一つは、 仮説を立てる ことです[貴裕, 2023]。 ドメイン知識を利用して仮説を立て、可視化によって誤りがないか確認[1]し次の分析に繋げることで、効果的に作業を進めることができます。 本ハンズオンではこの考えに則り、各可視化手法を取り上げる前に可能な限り[2]仮説を設定します。
初期設定#
以降では、マンガ・アニメ・ゲームデータを可視化するための初期設定を行います。 紙幅の都合のため、書籍版と一部構成が異なることにご注意ください。
Import#
必要なライブラリをImportします。
Show code cell content
# warningsモジュールのインポート
import warnings
# データ解析や機械学習のライブラリ使用時の警告を非表示にする目的で警告を無視
# 本書の文脈では、可視化の学習に議論を集中させるために選択した
# ただし、学習以外の場面で、警告を無視する設定は推奨しない
warnings.filterwarnings("ignore")
Show code cell content
# itertoolsモジュールのインポート
# 様々なパターンのループを効率的に実行可能
import itertools
# pathlibモジュールのインポート
# ファイルシステムのパスを扱う
from pathlib import Path
# numpy:数値計算ライブラリのインポート
# npという名前で参照可能
import numpy as np
# pandas:データ解析ライブラリのインポート
# pdという名前で参照可能
import pandas as pd
# plotly.expressのインポート
# インタラクティブなグラフ作成のライブラリ
# pxという名前で参照可能
import plotly.express as px
# plotly.graph_objectsからFigureクラスのインポート
# 型ヒントの利用を主目的とする
from plotly.graph_objects import Figure
型ヒントについてはこちらを参照ください。
定数#
本Notebookで用いる定数を定義します。 Pythonにおける定数の扱いについては、こちらを参照ください。
Show code cell content
# メディア展開データが保存されているディレクトリのパス
DIR_IN = Path("../../data/mix/input/")
# 分析結果の出力先ディレクトリのパス
DIR_OUT = DIR_IN.parent / "output" / Path.cwd().parts[-1] / "amounts"
Show code cell content
# 読み込み対象ファイル名の定義
# アニメ各話と原作マンガの作者の対応関係に関するファイル
FN_AE_CRT = "mix_ae_crt.csv"
# マンガ各話とアニメ作品の対応関係に関するファイル
FN_CE_AC = "mix_ce_ac.csv"
Show code cell content
# 可視化に関する設定値の定義
# 「年代」の集計単位
UNIT_YEARS = 10
Show code cell content
# plotlyの描画設定の定義
# plotlyのグラフ描画用レンダラーの定義
# Jupyter Notebook環境のグラフ表示に適切なものを選択
RENDERER = "plotly_mimetype+notebook"
Show code cell content
# 質的変数の描画用のカラースケールの定義
# Okabe and Ito (2008)基準のカラーパレット
# 色の識別性が高く、多様な色覚の人々にも見やすい色組み合わせ
# 参考URL: https://jfly.uni-koeln.de/color/#pallet
OKABE_ITO = [
"#000000", # 黒 (Black)
"#E69F00", # 橙 (Orange)
"#56B4E9", # 薄青 (Sky Blue)
"#009E73", # 青緑 (Bluish Green)
"#F0E442", # 黄色 (Yellow)
"#0072B2", # 青 (Blue)
"#D55E00", # 赤紫 (Vermilion)
"#CC79A7", # 紫 (Reddish Purple)
]
関数#
以下では、本Notebookで用いる関数を定義します。
Show code cell content
def show_fig(fig: Figure) -> None:
"""
所定のレンダラーを用いてplotlyの図を表示
Jupyter Bookなどの環境での正確な表示を目的とする
Parameters
----------
fig : Figure
表示対象のplotly図
Returns
-------
None
"""
# 図の周囲の余白を設定
# t: 上余白
# l: 左余白
# r: 右余白
# b: 下余白
fig.update_layout(margin=dict(t=25, l=25, r=25, b=25))
# 所定のレンダラーで図を表示
fig.show(renderer=RENDERER)
Show code cell content
def add_years_to_df(
df: pd.DataFrame, unit_years: int = UNIT_YEARS, col_date: str = "date"
) -> pd.DataFrame:
"""
データフレームにunit_years単位で区切った年数を示す新しい列を追加
Parameters
----------
df : pd.DataFrame
入力データフレーム
unit_years : int, optional
年数を区切る単位、デフォルトはUNIT_YEARS
col_date : str, optional
日付を含むカラム名、デフォルトは "date"
Returns
-------
pd.DataFrame
新しい列が追加されたデータフレーム
"""
# 入力データフレームをコピー
df_new = df.copy()
# unit_years単位で年数を区切り、新しい列として追加
df_new["years"] = (
pd.to_datetime(df_new[col_date]).dt.year // unit_years * unit_years
)
# 'years'列のデータ型を文字列に変更
df_new["years"] = df_new["years"].astype(str)
return df_new
Show code cell content
def resample_df_by_col_and_years(df: pd.DataFrame, col: str) -> pd.DataFrame:
"""
指定されたカラムと年数に基づき、データフレームを再サンプル
colとyearsの全ての組み合わせが存在するように0埋めを行う
この処理は、作図時にX軸方向の順序が変わることを防ぐために必要
Parameters
----------
df : pd.DataFrame
入力データフレーム
col : str
サンプリング対象のカラム名
Returns
-------
pd.DataFrame
再サンプルされたデータフレーム
"""
# 入力データフレームを新しい変数にコピー
df_new = df.copy()
# データフレームからユニークな年数一覧を取得
unique_years = df["years"].unique()
# データフレームからユニークなcolの値一覧を取得
unique_vals = df[col].unique()
# 一意なカラムの値と年数の全ての組み合わせに対して処理
for val, years in itertools.product(unique_vals, unique_years):
# 対象のカラムの値と年数が一致するデータを抽出
df_tmp = df_new[(df_new[col] == val) & (df_new["years"] == years)]
# 該当するデータが存在しない場合
if df_tmp.shape[0] == 0:
# 0埋めのデータを作成
default_data = {c: 0 for c in df_tmp.columns}
# col列についてはvalで埋める
default_data[col] = val
# years列についてはyearで埋める
default_data["years"] = years
# 新たなレコードとして追加
df_add = pd.DataFrame(default_data, index=[0])
# 0埋めのデータをデータフレームに追加
df_new = pd.concat([df_new, df_add], ignore_index=True)
return df_new
Show code cell content
def save_df_to_csv(df: pd.DataFrame, dir_save: Path, fn_save: str) -> None:
"""
DataFrameをCSVファイルとして指定されたディレクトリに保存する関数
Parameters
----------
df : pd.DataFrame
保存対象となるDataFrame
dir_save : Path
出力先ディレクトリのパス
fn_save : str
保存するCSVファイルの名前(拡張子は含めない)
"""
# 出力先ディレクトリが存在しない場合は作成
dir_save.mkdir(parents=True, exist_ok=True)
# 出力先のパスを作成
p_save = dir_save / f"{fn_save}.csv"
# DataFrameをCSVファイルとして保存する
df.to_csv(p_save, index=False, encoding="utf-8-sig")
# 保存完了のメッセージを表示する
print(f"DataFrame is saved as '{p_save}'.")
可視化例#
まず、可視化対象となるデータを読み込みましょう。
Show code cell content
# pandasのread_csv関数でCSVファイルの読み込み
df_ae_crt = pd.read_csv(DIR_IN / FN_AE_CRT)
棒グラフ#
5章の可視化例の結果、本書で扱うデータの範囲で最も多くのマンガ各話の掲載実績があるのは水島新司さんであることがわかりました。
それでは、マンガ原作アニメの放送話数はどうでしょうか?
本節では棒グラフを用いて、「自身のマンガが原作となったアニメ各話が最も多く放送されたのは水島新司さんである」という仮説を確認します。
棒グラフ ( Bar Chart ) は、主に質的変数と対応する数量を 棒の長さ で表す可視化手法でした。 棒を縦方向に並べることもありますし、横方向に並べることもあります。 質的変数の量を見る最も一般的な方法の一つです。 詳細は5章に整理してありますので、適宜復習しましょう。
Show code cell content
# 作図用の集計
# データフレームdf_ae_crtを'crtname'(マンガ作者名)ごとにグループ化し、
# 'aeid'(アニメエピソードID)のユニークな値の数を集計
# 集計結果を新しいカラム'name'としてデータフレームに追加
df_bar = df_ae_crt.groupby("crtname")["aeid"].nunique().reset_index(name="counts")
# 得られた集計結果を'counts'カラムの値が多い順に並び替え
# ascending=Falseで降順ソートを指定
# ignore_index=Trueで新しいインデックスを割り当て直す
# head(20)で上位20件のデータのみを抽出
df_bar = df_bar.sort_values("counts", ascending=False, ignore_index=True).head(20)
# データフレームdf_barのカラム名を変更
df_bar = df_bar.rename(columns={"crtname": "原作マンガ作者名", "counts": "アニメの合計放送話数"})
Show code cell content
# 可視化対象のDataFrameを確認
df_bar.head()
| 原作マンガ作者名 | アニメの合計放送話数 | |
|---|---|---|
| 0 | 青山剛昌 | 806 |
| 1 | 尾田栄一郎 | 783 |
| 2 | 岸本斉史 | 738 |
| 3 | 久保帯人 | 369 |
| 4 | 冨樫義博 | 336 |
Show code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_bar, DIR_OUT, "bar")
DataFrame is saved as '../../data/mix/output/09/amounts/bar.csv'.
Show code cell source
# plotly.expressを使用して棒グラフを作成
# 'df_bar'データフレームの'原作マンガ作者名'列をx軸、'アニメの合計放送話数'列をy軸に設定してプロット
fig = px.bar(df_bar, x="原作マンガ作者名", y="アニメの合計放送話数")
# show_fig関数を使用して図を表示
show_fig(fig)
上図は、原作マンガ作者別の合計アニメ各話数を表現した棒グラフです。
マンガ各話数の可視化結果と顔ぶれが全く異なるのが興味深いです。
このデータセットにおいて最も多くのアニメエピソードが放送されている原作者は、青山剛昌さんであることがわかりました。
つまり、「自身のマンガが原作となったアニメ各話が最も多く放送されたのは水島新司さんである」という仮説は誤りである可能性が高い[3]です。
では、青山剛昌さんのマンガ作品が原作となったアニメ作品について調べてみましょう。
Show code cell content
# 青山剛昌さんによる作品のデータを抽出し、アニメ作品名でグループ化
# 各アニメ作品について合計話数、最初の放送日、最後の放送日を集計
df_tmp = (
df_ae_crt[df_ae_crt["crtname"] == "青山剛昌"]
.groupby("acname")[["n_ae", "first_date", "last_date"]]
.first()
.reset_index()
)
# 最初の放送日でソートし、インデックスをリセット
df_tmp.sort_values("first_date", ignore_index=True)
| acname | n_ae | first_date | last_date | |
|---|---|---|---|---|
| 0 | 剣勇伝説YAIBA | 52 | 1993-04-09 | 1994-04-01 |
| 1 | 名探偵コナン | 729 | 1999-12-06 | 2016-12-24 |
| 2 | まじっく快斗 KID THE PHANTOM THIEF 第1話 「蘇る怪盗」 | 1 | 2010-04-17 | 2010-04-17 |
| 3 | まじっく快斗 1412 | 24 | 2014-10-04 | 2015-03-28 |
剣勇伝説YAIBA、名探偵コナンそしてまじっく快斗 1412と、2クール以上のアニメ作品を複数生み出していることがわかりました。
特に、名探偵コナンはアニメ作品の中でも特に各話数が多いものの一つであり、現在も放送が継続しています。
後段の処理で用いるため、アニメ各話数が多い作者一覧を保存しておきましょう。
Show code cell content
# 後段の処理で利用するため、アニメ各話数が多いマンガ作者一覧を保存
crtnames = df_bar["原作マンガ作者名"].values.tolist()
ちなみに、アニメ 各話 (aeid)数ではなくアニメ 作品 (acid)数はどうでしょうか?
Show code cell content
# 特番をアニメ作品から除外するために必要なエピソードの最小数を設定
min_nae = 10
# df_ae_crtデータフレームをアニメ作品ID(acid)でグループ化し、
# 各アニメに含まれるユニークなエピソードID(aeid)の数をカウント
df_ac_nae = df_ae_crt.groupby("acid")["aeid"].nunique().reset_index(name="n_ae")
# エピソード数がmin_nae以上のアニメ作品ID(acid)を抽出し、リストを作成
acids = df_ac_nae[df_ac_nae["n_ae"] >= min_nae]["acid"].unique().tolist()
Show code cell content
# acidがacidsに含まれる(つまりmin_nae話以上放送された)アニメ各話に対して
# 'crtname'(マンガ作者名)ごとにグループ化し、'acid'(アニメ作品ID)のユニークな値の数を集計
# 集計結果を新しいカラム'name'としてデータフレームに追加
df_bar2 = (
df_ae_crt[df_ae_crt["acid"].isin(acids)]
.groupby("crtname")["acid"]
.nunique()
.reset_index(name="counts")
)
# 得られた集計結果を'counts'カラムの値が多い順に並び替え
# ignore_index=Trueで新しいインデックスを割り当て直す
# head(20)で上位20件のデータのみを抽出
df_bar2 = df_bar2.sort_values("counts", ascending=False, ignore_index=True).head(20)
# データフレームdf_bar2のカラム名を変更
df_bar2 = df_bar2.rename(columns={"crtname": "原作マンガ作者名", "counts": "アニメ作品数"})
Show code cell content
# 可視化対象のDataFrameを確認
df_bar2.head()
| 原作マンガ作者名 | アニメ作品数 | |
|---|---|---|
| 0 | 小畑健 | 6 |
| 1 | 満田拓也 | 6 |
| 2 | 空知英秋 | 5 |
| 3 | 高橋留美子 | 5 |
| 4 | 大場つぐみ | 4 |
Show code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_bar2, DIR_OUT, "bar2")
DataFrame is saved as '../../data/mix/output/09/amounts/bar2.csv'.
Show code cell source
# plotly.expressを使用して棒グラフを作成
# 'df_bar2'データフレームの'マンガ作者名'列をx軸、'アニメ作品数'列をy軸に設定してプロット
fig = px.bar(df_bar2, x="原作マンガ作者名", y="アニメ作品数")
# show_fig関数を使用して図を表示
show_fig(fig)
上図は、原作マンガ作者別のアニメ作品数を表現した棒グラフです。
満田拓也さんと小畑健さんが最も多く、6作品アニメ化された実績があることがわかります。
では、満田拓也さんの内訳を見てみましょう。
Show code cell content
# 満田拓也さんによる作品のデータを抽出し、アニメ作品名でグループ化
# 各アニメ作品について合計話数、最初の放送日、最後の放送日を集計
df_tmp = (
df_ae_crt[df_ae_crt["crtname"] == "満田拓也"]
.groupby("acname")[["n_ae", "first_date", "last_date"]]
.first()
.reset_index()
)
# 最初の放送日でソートし、インデックスをリセット
df_tmp.sort_values("first_date", ignore_index=True)
| acname | n_ae | first_date | last_date | |
|---|---|---|---|---|
| 0 | メジャー | 26 | 2004-11-13 | 2005-05-07 |
| 1 | メジャー[第2期] | 30 | 2005-12-10 | 2007-01-03 |
| 2 | メジャー[第3期] | 27 | 2007-01-06 | 2007-06-30 |
| 3 | メジャー[第4期] | 26 | 2008-01-05 | 2008-06-28 |
| 4 | メジャー[第5期] | 25 | 2009-01-10 | 2009-06-27 |
| 5 | メジャー[第6期] | 26 | 2010-04-03 | 2010-09-25 |
メジャーの6期まで、それぞれ別のアニメ作品として扱われていることがわかりました。
このように、期数ごとに別のacidを対応付けられているアニメ作品は多くあります。
そこで、アニメシリーズ(asid)で再集計してみましょう。
Show code cell content
# 特番をアニメ作品から除外するために必要なエピソードの最小数を設定
min_nae = 10
# df_ae_crtデータフレームをアニメシリーズID(asid)でグループ化し、
# 各アニメに含まれるユニークなエピソードID(aeid)の数をカウント
df_as_nae = df_ae_crt.groupby("asid")["aeid"].nunique().reset_index(name="n_ae")
# エピソード数がmin_nae以上のアニメのアニメシリーズID(asid)を抽出し、リストを作成
asids = df_as_nae[df_as_nae["n_ae"] >= min_nae]["asid"].unique().tolist()
Show code cell content
# asidがasidsに含まれる(つまりmin_nae話以上放送された)アニメ各話に対して
# 'crtname'(マンガ作者名)ごとにグループ化し、'asid'(アニメシリーズID)のユニークな値の数を集計
# 集計結果を新しいカラム'name'としてデータフレームに追加
df_bar3 = (
df_ae_crt[df_ae_crt["asid"].isin(asids)]
.groupby("crtname")["asid"]
.nunique()
.reset_index(name="counts")
)
# 得られた集計結果を'counts'カラムの値が多い順に並び替え
# ignore_index=Trueで新しいインデックスを割り当て直す
# head(20)で上位20件のデータのみを抽出
df_bar3 = df_bar3.sort_values("counts", ascending=False, ignore_index=True).head(20)
# データフレームdf_bar3のカラム名を変更
df_bar3 = df_bar3.rename(columns={"crtname": "原作マンガ作者名", "counts": "アニメシリーズ数"})
Show code cell content
# 可視化対象のDataFrameを確認
df_bar3.head()
| 原作マンガ作者名 | アニメシリーズ数 | |
|---|---|---|
| 0 | 小畑健 | 4 |
| 1 | 瀬尾公治 | 3 |
| 2 | 冨樫義博 | 3 |
| 3 | 椎名高志 | 2 |
| 4 | 赤松健 | 2 |
Show code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_bar3, DIR_OUT, "bar3")
DataFrame is saved as '../../data/mix/output/09/amounts/bar3.csv'.
Show code cell source
# plotly.expressを使用して棒グラフを作成
# 'df_bar3'データフレームの'マンガ作者名'列をx軸、'アニメシリーズ数'列をy軸に設定してプロット
fig = px.bar(df_bar3, x="原作マンガ作者名", y="アニメシリーズ数")
# show_fig関数を使用して図を表示
show_fig(fig)
上図は、原作マンガ作者ごとにアニメシリーズ数を集計した棒グラフです。
本書で扱うデータにおいては、小畑健さんが最も多く4シリーズと関連付けられていることがわかりました。
ただしメディア展開データの基礎分析でも触れたように、アニメ作品データには多くの欠損があります。
(小畑健さん含め)各マンガ作者のアニメ化実績が過小に評価されている可能性が高いことにご注意ください。
集合棒グラフ#
既に「自身のマンガが原作となったアニメ各話が最も多く放送されたのは水島新司さんである」という仮説は誤りである可能性が高いことは確認できましたが、
本節では集合棒グラフを用いて別角度から分析してみましょう。
集合棒グラフ ( Grouped Bar Chart ) とは、新たな質的変数に応じて棒グラフの内訳を分割し、 並列に並べた 可視化手法でした。 詳細は5章にありますので、復習に役立ててください。
Show code cell content
# 可視化のための集計
# アニメ各話放送数が多い上位10名のマンガ家を抽出
df_gbar = df_ae_crt[df_ae_crt["crtname"].isin(crtnames[:10])].reset_index(drop=True)
# date列が欠損していないデータのみを集計対象とする
df_gbar = df_gbar[~df_gbar["date"].isna()].reset_index(drop=True)
# 'years'カラムをデータフレームに追加し、年代情報を含める
# つぎに、年代(years)をカテゴリカルなデータとして扱うために文字列型に変換
# さらに、'years'カラムの欠損値を持つ行を削除し、インデックスをリセット
df_gbar = add_years_to_df(df_gbar)
df_gbar["years"] = df_gbar["years"].astype(str)
df_gbar = df_gbar[~df_gbar["years"].isna()].reset_index(drop=True)
# マンガ作者名と年度でグループ化し、各グループ内のユニークな放送エピソード数をカウント
df_gbar = df_gbar.groupby(["crtname", "years"])["aeid"].nunique().reset_index()
# # 'crtname'と'years'カラムを基準にして、データをアップサンプリング
df_gbar = resample_df_by_col_and_years(df_gbar, "crtname")
# マンガ作者の名前のリストに基づいて、ソート用の順序カラムを作成
# 'order'と'years'カラムに基づいてデータフレームをソートし、作図用に整える
df_gbar["order"] = df_gbar["crtname"].apply(lambda x: crtnames.index(x))
df_gbar = df_gbar.sort_values(["order", "years"], ignore_index=True)
# 作図しやすいよう列名を変更
df_gbar = df_gbar.rename(columns={"crtname": "原作マンガ作者名", "aeid": "アニメの合計放送話数", "years": "年代"})
Show code cell content
# 可視化対象のDataFrameを確認
df_gbar.head()
| 原作マンガ作者名 | 年代 | アニメの合計放送話数 | order | |
|---|---|---|---|---|
| 0 | 青山剛昌 | 1990 | 55 | 0 |
| 1 | 青山剛昌 | 2000 | 401 | 0 |
| 2 | 青山剛昌 | 2010 | 350 | 0 |
| 3 | 尾田栄一郎 | 1990 | 9 | 1 |
| 4 | 尾田栄一郎 | 2000 | 432 | 1 |
Show code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_gbar, DIR_OUT, "gbar")
DataFrame is saved as '../../data/mix/output/09/amounts/gbar.csv'.
Show code cell source
# px.barを使用して、マンガ作者ごとの年代別アニメ放送話数の棒グラフを作成
# 'df_gbar'データフレームの'原作マンガ作者名'列をx軸に、'アニメの合計放送話数'列をy軸に設定
# '年代'列を色分けの基準にし、Portlandの発散色スキームを使用して色分け
# barmodeを"group"に設定して、年代ごとにグループ化された棒グラフを表示
fig = px.bar(
df_gbar,
x="原作マンガ作者名",
y="アニメの合計放送話数",
color="年代",
color_discrete_sequence=px.colors.diverging.Portland,
barmode="group",
)
# 作成した棒グラフを表示
show_fig(fig)
上図は、マンガ作者の合計アニメ放送話数を年代別に表現した集合棒グラフです。
上位3名(青山剛昌さん、尾田栄一郎さん、そして岸本斉史さん)は2000年代以降のアニメ放送話数が特に多いことがわかりました。
逆に、1990年代のアニメ放送話数が多い原作マンガ作者として、冨樫義博さん、さとうふみやさん、そして天樹征丸さんが目立った存在であることがわかります。
なお、集合棒グラフはグループを並列に並べるため、比較対象とする質的変数(上図「原作マンガ作者名」)を多く取れません。 棒グラフでは20名を可視化対象としていましたが、ここではその半分の10名を対象としていることに注意してください。
積上げ棒グラフ#
次に、積上げ棒グラフを用いて同様の可視化をしてみましょう。
積上げ棒グラフ ( Stacked Bar Chart ) とは、 新たな質的変数に応じて棒グラフの内訳を分割し、 直列に並べた 可視化手法でした。 詳細は5章で取り上げましたので、復習に役立ててください。
Show code cell source
# px.barを使用して、マンガ作者ごとの年代別アニメ放送話数の積み上げ棒グラフを作成
# 'df_gbar'データフレームの'原作マンガ作者名'列をx軸に、'アニメの合計放送話数'列をy軸に設定
# '年代'列で色分けし、Portlandの発散色スキームを使用して色分け
# barmodeを"stack"に設定して、年代ごとに積み上げられた棒グラフを表示
fig = px.bar(
df_gbar,
x="原作マンガ作者名",
y="アニメの合計放送話数",
color="年代",
color_discrete_sequence=px.colors.diverging.Portland,
barmode="stack",
)
# 作成した棒グラフを表示
show_fig(fig)
上図は、マンガ作者の合計アニメ放送話数を年代別に表現した積上げ棒グラフです。
多くの原作マンガ作者にとって、2000年代以降に特にアニメ放送話数が多くなっていることがわかりました。
1990年代からバランスよく放送されているマンガ作者として、先に取り上げた冨樫義博さん、さとうふみやさん、そして天樹征丸さんが異質であることがわかります。
ヒートマップ#
集合棒グラフや積上げ棒グラフでは、 10年 を最小単位として各話数を集計したため、各マンガ作品の細かい時間粒度の各話数の推移を確認することはできませんでした。 特に集合棒グラフでは、二つの年代に跨る期間に集中的に放送されたアニメ作品に関して、実際よりも各話数が少ない印象を与えてしまう可能性があります。
そこで本項ではヒートマップを用いて、より細かい時間粒度で各話数を可視化します。
ヒートマップ ( Heatmap ) とは、主に複数の質的変数の組合せに対して、数量を 色 で表す可視化手法でした。 (具体的な数値をアノテーションしない限り)定量的な比較は難しいですが、全体像を俯瞰したい場合は非常に便利です。 また、非常に多くの組合せに対して数量を表現できる、という強みもあります。 詳細は5章にて取り上げましたので、復習に役立ててください。
本項ではこの特長を利用して、 1年単位 の各話数を可視化します。
Show code cell content
# 日付が欠損していないデータのみを選択
df_hm = df_ae_crt[~df_ae_crt["date"].isna()].reset_index(drop=True)
# df_hmに年代(years)を追加
df_hm = add_years_to_df(df_hm, 1)
# df_hmをdate順でソートし、全crtnameを事前に抽出しておく、時間順に並んだヒートマップを作成するための工夫
crtnames = df_hm.sort_values(["date", "crtname"], ignore_index=True)["crtname"].unique()
Show code cell content
# マンガ作者名と年代ごとに異なるaeidの数を集計
df_hm = df_hm.groupby(["crtname", "years"])["aeid"].nunique().reset_index(name="n_ae")
# crtnameのうち、df_barの原作マンガ作者名の上位20人を抽出
# 直接list(df_bar["原作マンガ作品名"])[:20]を用いないのはccnamesの順序を維持するため
crtnames = [
crtname for crtname in crtnames if crtname in list(df_bar["原作マンガ作者名"].unique())[:20]
]
# df_hmをフィルタリングして、crtnamesに含まれるcrtnameのみを選択
df_hm = df_hm[df_hm["crtname"].isin(crtnames)].reset_index(drop=True)
# 「crtname」列と「years」列を使ってdf_gbarをリサンプリング
df_hm = resample_df_by_col_and_years(df_hm, "crtname")
# 「crtname」列をカテゴリカルデータとして扱い、crtnamesの順序で並べ替え
df_hm["crtname"] = pd.Categorical(df_hm["crtname"], categories=crtnames, ordered=True)
# 「crtname」と「years」でソートし、インデックスをリセット
df_hm = df_hm.sort_values(["crtname", "years"], ignore_index=True)
# 可視化用に列名をわかりやすい日本語の名前に変更
df_hm = df_hm.rename(
columns={"crtname": "原作マンガ作者名", "years": "放送年", "n_ae": "アニメ各話数"}
)
Show code cell content
# 可視化対象のDataFrameを確認
df_hm.head()
| 原作マンガ作者名 | 放送年 | アニメ各話数 | |
|---|---|---|---|
| 0 | 冨樫義博 | 1992 | 12 |
| 1 | 冨樫義博 | 1993 | 49 |
| 2 | 冨樫義博 | 1994 | 50 |
| 3 | 冨樫義博 | 1995 | 1 |
| 4 | 冨樫義博 | 1997 | 0 |
Show code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_hm, DIR_OUT, "hm")
DataFrame is saved as '../../data/mix/output/09/amounts/hm.csv'.
Show code cell source
# df_hmデータを用いて年代とマンガ作者名に基づく密度ヒートマップを作成
# アニメ各話数を色の濃淡で表現し、ヒートマップの高さを500に設定
fig = px.density_heatmap(
df_hm, x="放送年", y="原作マンガ作者名", z="アニメ各話数", height=500
)
# ヒートマップのカラーバーにタイトル「アニメ各話数」を設定
fig.update_layout(coloraxis_colorbar={"title": "アニメ各話数"})
# 作成したヒートマップを表示する関数を実行
show_fig(fig)
上図は、原作マンガ作者ごとの年間アニメ放送話数の推移を表現したヒートマップです。 色が明るいほど年間アニメ各話数が多かったことを表します。
一つのアニメ作品が週1回1年間レギュラー放送された場合は、 50話 程度の色合い、つまりオレンジ色になるはずです。
しかし、青山剛昌さんに関しては2014年から2015年まで年間60話以上が放送されていたことがわかりました。
改めて青山剛昌さん原作のアニメ作品について調べてみましょう。
Show code cell content
# 青山剛昌さんによる作品のデータを抽出し、アニメ作品名でグループ化
# 各アニメ作品について合計話数、最初の放送日、最後の放送日を集計
df_tmp = (
df_ae_crt[df_ae_crt["crtname"] == "青山剛昌"]
.groupby("acname")[["n_ae", "first_date", "last_date"]]
.first()
.reset_index()
)
# 最初の放送日でソートし、インデックスをリセットして表示
df_tmp.sort_values("first_date", ignore_index=True)
| acname | n_ae | first_date | last_date | |
|---|---|---|---|---|
| 0 | 剣勇伝説YAIBA | 52 | 1993-04-09 | 1994-04-01 |
| 1 | 名探偵コナン | 729 | 1999-12-06 | 2016-12-24 |
| 2 | まじっく快斗 KID THE PHANTOM THIEF 第1話 「蘇る怪盗」 | 1 | 2010-04-17 | 2010-04-17 |
| 3 | まじっく快斗 1412 | 24 | 2014-10-04 | 2015-03-28 |
長寿作品である名探偵コナンに加え、2014年10月からまじっく快斗 1412が2クール放送されていることがわかりました。
局在的なデータの多寡を見つけやすくなることは、ヒートマップを用いた可視化の利点の一つと言えるでしょう。